<template>
  <div id="panel" ref="panelRef">
    <header class="panel-header" ref="panelHeaderRef">
      <div class="header-left">{{ title }}</div>

      <div style="float: right">
        <span v-if="isFull" @click="handleIconClick" class="icon-hover"><svg-icon icon-class="narrow"/></span>
        <span v-else @click="handleIconClick" class="icon-hover">
          <svg-icon icon-class="full" class="full-icon"/>
        </span>
        <span @click="handleClose" class="icon-hover"><svg-icon icon-class="close"/></span>
      </div>
    </header>

    <main class="panel-main" ref="panelMainRef">
      <div id="xterm" ref="terminal"></div>
    </main>
  </div>
</template>

<script>
import {Terminal} from "xterm";
import {FitAddon} from 'xterm-addon-fit'
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
import {utf8_to_b64, b64_to_utf8} from '@/utils/utils'
import {ElMessage} from "element-plus";

const packResize = (cols, rows) =>
    JSON.stringify({
      type: 'resize',
      cols: cols,
      rows: rows
    })

export default {
  name: "Terminal",
  components: {SvgIcon},

  props: {
    title: {
      default: "Title",
      type: String,
      required: false,
    },

    curUserID: {
      default: "",
      type: Number,
      required: true,
    },

    clientName: {
      default: "",
      type: String,
      required: true,
    },

    socketURI: {
      type: String,
      default: 'ws://127.0.0.1:8001/ssh/ws'
    },
  },

  mounted() {
    // 网页内容不可选中
    document.onselectstart = function () {
      return false
    }
    this.handleDivDrag()

    this.initTerm()
    this.initSocket()

    this.onTerminalResize()
    this.onTerminalKeyPress()
  },

  beforeUnmount() {
    this.handleClose()
  },

  data() {
    return {
      isFull: false,
      ws: {},
      fitAddon: {},
      attachAddon: {},
      term: {},
      command: [],

      first: true,
      initText: '连接中...\r\n',
      option: {
        fontSize: 20,
        fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
        scrollback: 5, //终端中的回滚量
        disableStdin: false, //是否应禁用输入
        cursorStyle: "block", //光标样式
        cursorBlink: true, //光标闪烁
        fontWeight: 'bold',
        tabStopWidth: 8, //制表宽度
        theme: {
          foreground: "white", //字体
          background: "#060101", //背景色
          cursor: "help", //设置光标
          selectionBackground: 'red'
        },
      }
    }
  },

  methods: {
    handleIconClick() {
      let panel = this.$refs.panelRef
      if (!this.isFull) {
        panel.style.width = '100%'
        panel.style.height = '100%'
      } else {
        panel.style.width = '600px'
        panel.style.height = '400px'
      }
      panel.style.left = '50%'
      panel.style.top = '50%'
      this.isFull = !this.isFull
      this.fitAddon.fit()

      setInterval(() => {
        this.fitAddon.fit()
      }, 2000)

    },

    handleClose() {
      this.removeResizeListener()

      this.ws.send(JSON.stringify({
        type: 'stdin',
        data: utf8_to_b64('exit')
      }))

      this.ws.close()
      this.term && this.term.dispose()  // dispose 会报错
      document.getElementById("terminal@@@@@@@@@@@@@@@@").parentNode.removeChild(document.getElementById("terminal@@@@@@@@@@@@@@@@"));

    },

    handleDivDrag() {
      let panel = this.$refs.panelRef
      let panelHeader = this.$refs.panelHeaderRef
      let width = this.$refs.panelRef.clientWidth
      let height = this.$refs.panelRef.clientHeight
      let halfWidth = 400, halfHeight = 300

      panelHeader.addEventListener('mousedown', function (e) {
        let x = e.pageX - panel.offsetLeft
        let y = e.pageY - panel.offsetTop

        document.addEventListener('mousemove', move)

        function move(e) {
          let tmpX = e.pageX - x
          let tmpY = e.pageY - y

          if (tmpX <= halfWidth) {
            tmpX = halfWidth
          }

          if (tmpY <= halfHeight) {
            tmpY = halfHeight
          }

          if (window.innerWidth - tmpX <= width - halfWidth) {
            tmpX = window.innerWidth - width + halfWidth
          }
          if (window.innerHeight - tmpY <= height - halfHeight) {
            tmpY = window.innerHeight - height + halfHeight
          }

          panel.style.left = tmpX + 'px'
          panel.style.top = tmpY + 'px'
        }

        document.addEventListener('mouseup', function (e) {
          document.removeEventListener('mousemove', move)
        })
      })
    },

    // xterm ----------------------------------------------------------------------------------

    initTerm() {
      let term = new Terminal(this.option);
      const fitAddon = new FitAddon();
      term.loadAddon(fitAddon);
      term.open(this.$refs.terminal);
      fitAddon.fit();
      term.focus();
      this.term = term
      this.fitAddon = fitAddon

      setTimeout(() => {
        this.fitAddon.fit()
      }, 500); // 必须延时处理
    },

    // socket
    initSocket() {
      let socketURL = this.socketURI + '?name=' + this.clientName + '&userID=' + this.curUserID

      this.term.write(this.initText)
      this.ws = new WebSocket(socketURL);
      this.onOpenSocket()
      this.onCloseSocket()
      this.onErrorSocket()
      this.term._initialized = true
      this.onMessageSocket()
    },

    onTerminalKeyPress() {
      this.term.onData(data => {
        this.isWsOpen() && this.ws.send(JSON.stringify({
          type: 'stdin',
          data: utf8_to_b64(data)
        }))
      })
    },

    isWsOpen() {
      return this.ws && this.ws.readyState === 1
    },

    // resize 相关
    resizeRemoteTerminal() {
      const {cols, rows} = this.term
      // console.log('列数、行数设置为：', cols, rows)
      // 调整后端终端大小 使后端与前端终端大小一致
      this.isWsOpen() && this.ws.send(packResize(cols, rows))
    },

    removeResizeListener() {
      window.removeEventListener('resize', this.onResize)
    },

    onTerminalResize() {
      window.addEventListener('resize', this.onResize)
      this.term.onResize(this.resizeRemoteTerminal)
    },

    // 打开连接
    onOpenSocket() {
      this.ws.onopen = () => {
        ElMessage({
          message: "打开终端",
          type: 'success'
        })

        // this.ws.send(JSON.stringify({type: "addr", data: utf8_to_b64('192.168.1.200' + ':' + 22)}));
        // socket.send(JSON.stringify({ type: "term", data: utf8_to_b64("linux") }));
        // this.ws.send(JSON.stringify({type: "login", data: utf8_to_b64('root')}));
        // this.ws.send(JSON.stringify({type: "password", data: utf8_to_b64('union110')}));
        this.term.reset()
        // setTimeout(() => {
        //   this.resizeRemoteTerminal()
        // }, 500)
      }
    },

    // 关闭连接
    onCloseSocket() {
      this.ws.onclose = () => {
        ElMessage({
          message: "关闭终端",
          type: 'success',
        })
        this.term.write('未连接， 刷新后重连...\r\n');
      }
    },

    // 连接错误
    onErrorSocket() {
      this.ws.onerror = () => {
        this.term.write('连接失败，请刷新！')
      }
    },

    // 接收消息
    onMessageSocket() {
      this.ws.onmessage = res => {
        // console.log('res: ', res)

        const msg = JSON.parse(res.data)
        const term = this.term
        // console.log("receive: " + data)
        // 第一次连接成功将 initText 清空
        if (this.first) {
          this.first = false
          term.reset()
          term.element && term.focus()
          this.resizeRemoteTerminal()
        }
        this.term.write(b64_to_utf8(msg.data))
      }
    },

  }
}
</script>

<style scoped lang="scss">
#panel {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 800px;
  height: 600px;
  transform: translate(-50%, -50%);
}

.panel-header {
  line-height: 30px;
  font-weight: bold;
  background-color: #caccd1;
  height: 30px;
  overflow: hidden;

  &:hover {
    cursor: move;
  }

  span {
    padding: 10px;

    &:hover {
      background-color: #caccd1;
    }
  }
}

.header-left {
  float: left;
  padding: 0 10px;

  &:hover {
    cursor: default;
  }
}

.full-icon {
  width: 18px;
  height: 18px;
}

.icon-hover {
  &:hover {
    cursor: default;
  }
}

.panel-main {
  height: calc(100% - 30px);
  width: auto;
  background-color: #f3f4f7;
}

#xterm {
  width: 100%;
  height: 100%;
  z-index: 999;
}
</style>
